package executor

import (
	"fmt"
	"reflect"
	"strings"

	"gitee.com/zhi/binexpr/ast"
	"gitee.com/zhi/binexpr/executor/internal/operand"
)

func reflectPropValueFromStruct(objVal reflect.Value, prop string) (any, error) {
	objType := objVal.Type()
	for i := 0; i < objType.NumField(); i++ {
		objFieldType := objType.Field(i)
		if !objFieldType.IsExported() {
			continue
		}
		if objFieldType.Anonymous && objVal.Field(i).Kind() == reflect.Struct {
			if v, err := reflectPropValueFromStruct(objVal.Field(i), prop); err == nil {
				return v, nil
			}
		}
		jsonName := objFieldType.Tag.Get("json")
		if jsonName == "-" {
			continue
		}
		if prop == jsonName || (len(jsonName) == 0 && strings.EqualFold(prop, objFieldType.Name)) {
			return objVal.Field(i).Interface(), nil
		}
	}
	return nil, fmt.Errorf("no such field is found: %s", prop)
}

func reflectPropValue(obj any, prop string) (any, error) {
	objVal := reflect.ValueOf(obj)
	// get the actual value.
	for objVal.Kind() == reflect.Pointer || objVal.Kind() == reflect.Interface {
		objVal = objVal.Elem()
	}
	// get prop according to the underly type.
	switch objVal.Kind() {
	case reflect.Map:
		v := objVal.MapIndex(reflect.ValueOf(prop))
		return v.Interface(), nil
	case reflect.Struct:
		return reflectPropValueFromStruct(objVal, prop)
	default:
		return nil, fmt.Errorf("unsupported object type: %T", obj)
	}
}

func resolveObjPropExpr(e *Executor, expr *ast.ObjPropExpr, scope Scope) (any, error) {
	obj, err := e.Execute(expr.Obj, scope)
	if err != nil {
		return nil, err
	}
	if m, ok := obj.(map[string]any); ok {
		return m[expr.Prop], nil
	}
	return reflectPropValue(obj, expr.Prop)
}

func reflectIndexValue(obj any, idx int) (any, error) {
	objVal := reflect.ValueOf(obj)
	// get the actual value.
	for objVal.Kind() == reflect.Pointer || objVal.Kind() == reflect.Interface {
		objVal = objVal.Elem()
	}
	// get index value according to the underly type.
	switch objVal.Kind() {
	case reflect.Array, reflect.String, reflect.Slice:
		if idx >= objVal.Len() {
			return nil, fmt.Errorf("index out of bounds: %d", idx)
		}
		return objVal.Index(idx).Interface(), nil
	default:
		return nil, fmt.Errorf("unsupported object type: %T", obj)
	}
}

func resolveObjIndexExpr(e *Executor, expr *ast.ObjIndexExpr, scope Scope) (any, error) {
	obj, err := e.Execute(expr.Obj, scope)
	if err != nil {
		return nil, err
	}
	idx, err := e.Execute(expr.Index, scope)
	if err != nil {
		return nil, err
	}
	switch idx := idx.(type) {
	case string:
		return reflectPropValue(obj, idx)
	case fmt.Stringer:
		return reflectPropValue(obj, idx.String())
	default:
		idxOp, err := operand.New(idx)
		if err != nil {
			return nil, err
		}
		switch i := idxOp.(type) {
		case operand.Int:
			return reflectIndexValue(obj, int(i))
		case operand.Uint:
			return reflectIndexValue(obj, int(i))
		default:
			return nil, fmt.Errorf("unsupported index type %T", idxOp)
		}
	}
}
